/**
 * @class UI显示跟随
 * @author DuskyHuang 圣子
 * @description 提供跟随3D物体、设置偏移、锁定在画布内等UI提示功能
*/

import { Node, Camera, Component, UITransform, view, Vec3, Vec2, _decorator } from 'cc';
import { EDITOR } from 'cc/env';
import { Notifications } from '../../configs';
import { notifier } from '../../core/boot';
import { findCamera, group, serialize } from '../../utils';
import { Range } from '../misc/limitation';
const { ccclass } = _decorator;
function usePerspective(this: HeadUpDisplay) { return this.perspective; }
function showRange(this: HeadUpDisplay) { return this.useVisibleRange; }

@ccclass('cfw.HeadUpDisplay')
export class HeadUpDisplay extends Component {

    @group("Follow", "跟随目标（3D）", Node) public target: Node = null;
    @group("Follow", "主相机（3D）", Camera) public mainCamera: Camera = null;

    @group("Offset", "跟随偏移（3D）") public offset: Vec3 = new Vec3;
    @group("Offset", "依据绝对缩放") public scalable: boolean = false;
    @group("Offset", "UI偏移（2D）") public offsetUI: Vec2 = new Vec2;

    @group("Scale", "启用透视缩放") public perspective: boolean = false;
    @group("Scale", "标准距离（透视缩放=1）", usePerspective) public distance: number = 1;
    @group("Scale", "标准大小", usePerspective) public scaleRatio: number = 1;
    @group("Scale", "缩放范围", usePerspective) public scaleRange: Range = new Range(0, 100);

    @serialize private _useInitDistance: boolean = false;
    @group("Scale", "初始距离作为标准距离", usePerspective)
    public get useInitDistance(): boolean { return this._useInitDistance; }
    public set useInitDistance(arg: boolean) {
        this._useInitDistance = arg;
        this._setInitDistance();
    }
    @group("Visible", "约束在画布内")
    public alwaysInCanvas: boolean = false;
    @group("Visible", "目标必须激活")
    public syncActive: boolean = false;

    @group("Visible", "启用范围可见")
    public useVisibleRange: boolean = false;

    @group("Visible", "可见范围", showRange) public visibleRange: Range = new Range(0, 100);

    public get invalid(): boolean { return !this.node.parent || !this.mainCamera || !this.target || (this.syncActive && !this.target.activeInHierarchy); }

    private _setInitDistance(): void {
        if (this.invalid) return;
        if (this.perspective && this.useInitDistance) {
            this.distance = Vec3.distance(
                this.mainCamera.node.getWorldPosition(v3a),
                this.target.getWorldPosition(v3b)
            )
        }
    }

    protected onLoad(): void {
        this.mainCamera = this.mainCamera ?? findCamera();
        if (!EDITOR) notifier.attach(Notifications.Time.POST_TICK, this.tick, this);
    }

    protected onDestroy(): void { if (!EDITOR) notifier.unload(this); }

    protected start(): any {
        if (this.invalid) return this.node.active = false;
        this._setInitDistance();
    }

    protected tick(dt?: time.sec, tick?: num.int): any {
        if (this.invalid) return this.node.active = false;
        const { offset, offsetUI: { x, y }, useVisibleRange, perspective, scalable } = this;
        if (useVisibleRange || perspective) {
            dist = Vec3.distance(this.mainCamera.node.getWorldPosition(v3a), this.target.getWorldPosition(v3b));
        }
        if (this.useVisibleRange) {
            const { min, max } = this.visibleRange;
            this.node.active = min <= dist && dist <= max;
            if (!this.node.active) return;
        }
        scalable ? Vec3.multiply(v3b, offset, this.target.getWorldScale(v3b)) : Vec3.copy(v3b, offset);
        this.mainCamera.convertToUINode(Vec3.add(v3a, this.target.getWorldPosition(v3a), v3b), this.node.parent, v3a);
        if (v3a.z > 1) {
            this.node.active = false;
        } else {
            this.node.setPosition(v3a.x + x, v3a.y + y, v3a.z);
            if (this.perspective) {
                let ratio = (dist === 0 ? Number.MAX_VALUE : this.distance / dist) * this.target.worldScale.y;
                const { min, max } = this.scaleRange;
                ratio *= this.scaleRatio;
                ratio += (1 - this.scaleRatio);
                ratio = Math.clamp(ratio, min, max);
                this.node.setScale(ratio, ratio);
            }
            if (this.alwaysInCanvas) {
                this.node.getWorldPosition(v3a);
                this.node.getScale(v3b);
                const uit = this.node.getComponent(UITransform);
                const { width, height } = uit.contentSize;
                let { x, y, width: w, height: h } = uit.getBoundingBoxToWorld();
                const { width: W, height: H } = view.getVisibleSize();
                const hw = width * v3b.x / 2, hh = height * v3b.y / 2;
                this.node.setWorldPosition(
                    x < 0 ? hw : (x + w > W) ? (W - hw) : v3a.x,
                    y < 0 ? hh : (y + h > H) ? (H - hh) : v3a.y,
                    v3a.z);
            }
            this.node.active = true;
        }
    }
}

let dist = 0;
const v3a = new Vec3;
const v3b = new Vec3;